package org.apache.ocean.solr.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ocean.XML;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.Verifier;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

/**
 * 
 * @author Jason Rutherglen
 */
public class XMLUtil {
	public static Logger log = Logger.getLogger(XMLUtil.class.getName());

	public XMLUtil() {
	}

	public static Collection<Attribute> getAttributes(Element element) {
		return (Collection<Attribute>)element.getAttributes();
	}
	
	public static String printAndDetach(Element element) throws Exception {
		List<Element> list = element.cloneContent();
		Element root = null;
		if (list.size() == 1) {
			root = list.get(0);
		} else {
			root = new Element("root");
			root.addContent(list);
		}
		return outputElement(root);
	}

	public static Element reflectToElement(String name, Object object) throws Exception {
		// log.info("reflectToElement: "+name+" object:
		// "+object.getClass().getName());
		Element element = new Element(name);
		for (Field field : FieldUtil.getFields(object.getClass())) {
			String fieldName = field.getName();
			// log.info("fieldName: "+fieldName);
			Object fieldValue = field.get(object);
			if (fieldValue != null) {
				if (fieldValue instanceof Collection) {
					for (Object o : (Collection) fieldValue) {
						if (o instanceof CElement) {
							element.addContent(((CElement) o).toElement());
						}
					}
				}
				XML xml = (XML) field.getAnnotation(XML.class);
				if (xml != null && !xml.exclude()) {
					if (xml.binary() && fieldValue instanceof Serializable) {
						Base64 base64 = new Base64();
						byte[] bytes = base64.encode(SerializationUtils.serialize((Serializable) fieldValue));
						XMLUtil.createTextElement(fieldName, new String(bytes, "UTF-8"), element);
					} else {
						XMLUtil.createTextElement(fieldName, fieldValue, element);
					}
				} else if (xml != null && xml.exclude()) {
					continue;
				} else {
					XMLUtil.createTextElement(fieldName, fieldValue, element);
				}
			}
		}
		return element;
	}

	public static Object reflectToObject(Class clazz, Element root) throws Exception {
		Object object = clazz.newInstance();
		return reflectToObject(object, root);
	}

	public static Object reflectToObject(Object object, Element root) throws Exception {
		for (Element element : getChildren(root)) {
			String name = element.getName();
			Field field = FieldUtil.getField(object.getClass(), name);
			if (field == null)
				throw new Exception("could not find field: " + name + " in " + object.getClass().getName());
			XML xml = (XML) field.getAnnotation(XML.class);
			Object value = null;
			if (xml == null) {
				if (field.getType().isAssignableFrom(Collection.class)) {

				} else {
					String text = element.getText();
					value = getProperValue(field, text);
				}
			} else if (xml != null && xml.binary()) {
				String text = element.getText();
				if (StringUtils.isNotBlank(text)) {
					Base64 base64 = new Base64();
					byte[] bytes = base64.decode(text.getBytes("UTF-8"));
					value = SerializationUtils.deserialize(bytes);
				}
			}
			FieldUtil.setFieldValue(object, name, value);
		}
		return object;
	}

	public static Object getProperValue(Field field, String string) throws Exception {
		if (!field.getType().isAssignableFrom(String.class)) {
			if (field.getType().isAssignableFrom(URL.class)) {
				return new URL(string);
			} else if (field.getType().isAssignableFrom(Date.class)) {
				return DateFieldType.parseDate(string);
			} else if (field.getType().isAssignableFrom(File.class)) {
				return new File(string);
			} else if (field.getType().isAssignableFrom(Long.class)) {
				return new Long(string);
			} else if (field.getType().isAssignableFrom(Double.class)) {
				return new Double(string);
			}
			throw new Exception("unknown type " + field.getType().getName());
		} else
			return string;
	}

	public static void addAll(List<Element> children, Element parent) {
		for (Element element : children) {
			parent.addContent(element);
		}
	}

	public static void printList(String rootName, List list, PrintWriter writer) throws Exception {
		Format format = Format.getPrettyFormat();
		format.setLineSeparator("\n");
		XMLOutputter outputter = new XMLOutputter(format);
		Element root = new Element(rootName);
		for (Object object : list) {
			if (object instanceof CElement) {
				CElement cElement = (CElement) object;
				root.addContent(cElement.toElement());
			}
		}
		Document document = new Document();
		document.addContent(root);
		outputter.output(document, writer);
	}

	public static List<Element> getChildren(String xml) throws Exception {
		return getChildren(parseElement(xml));
	}

	public static List<Element> getChildren(String name, Element element) {
		List<Element> elements = (List<Element>) element.getChildren(name);
		if (element == null)
			return new ArrayList<Element>(0);
		else
			return elements;
	}

	public static List<Element> getChildren(Element element) {
		List<Element> elements = (List<Element>) element.getChildren();
		if (element == null)
			return new ArrayList<Element>(0);
		else
			return elements;
	}

	public static Element getFirstChild(Element element) {
		return (Element) Util.getFirst(element.getChildren());
	}

	public static void setAttribute(String name, Object value, Element element) {
		if (value == null) {
			return;
		}
		if (value instanceof Date) {
			value = DateFieldType.formatDate((Date) value);
		}
		element.setAttribute(name, value.toString());
	}

	public static java.net.URL getChildURL(String name, Element element) {
		String childText = element.getChildText(name);
		try {
			return new java.net.URL(childText);
		} catch (Throwable th) {
		}
		return null;
	}

	public static Element getChild(String name, Element root) {
		return root.getChild(name);
	}

	public static java.math.BigInteger getChildBigInteger(String name, Element element) {
		String childText = element.getChildText(name);
		try {
			return new java.math.BigInteger(childText);
		} catch (Throwable throwable) {
		}
		return null;
	}

	public static Long getChildLong(String name, Element element) throws NumberFormatException {
		String childText = element.getChildText(name);
		return new Long(childText);
	}

	public static String getAttributeString(String name, Element element) {
		String text = element.getAttributeValue(name);
		return text;
	}
  
	public static Integer getAttributeInteger(String name, Element element) throws NumberFormatException {
		String text = element.getAttributeValue(name);
		if (text == null || text.equals("")) {
			return null;
		}
		return new Integer(text);
	}
	
	public static Long getAttributeLong(String name, Element element) throws NumberFormatException {
		String text = element.getAttributeValue(name);
		if (text == null || text.equals("")) {
			return null;
		}
		return new Long(text);
	}

	public static Date getAttributeDate(String name, Element element) throws ParseException {
		String text = element.getAttributeValue(name);
		if (text == null || text.equals("")) {
			return null;
		}
		return DateFieldType.parseDate(text);
	}

	public static Boolean getChildBoolean(String name, Element element) {
		String text = element.getChildText(name);
		if (StringUtils.isBlank(text)) {
			return null;
		}
		return BooleanUtils.toBooleanObject(text);
	}

	public static boolean getAttributeBooleanPrimitive(String name, Element element) {
		String text = element.getAttributeValue(name);
		if (StringUtils.isBlank(text)) {
			return false;
		}
		return BooleanUtils.toBooleanObject(text);
	}

	public static Boolean getAttributeBoolean(String name, Element element) {
		String text = element.getAttributeValue(name);
		if (StringUtils.isBlank(text)) {
			return null;
		}
		return BooleanUtils.toBooleanObject(text);
	}

	public static Date getChildDate(String name, Element element) throws ParseException {
		String text = element.getChildText(name);
		if (text == null || text.equals("")) {
			return null;
		}
		return parseDate(text);
	}

	public static Date parseDate(String dateStr) throws ParseException {
		if (org.apache.commons.lang.StringUtils.isEmpty(dateStr)) {
			return null;
		}
		return DateFieldType.parseDate(dateStr);
	}

	public static String formatDate(Date date) {
		if (date == null) {
			return "";
		}
		return DateFieldType.formatDate(date);
	}

	public static String getChildText(String name, Element element) {
		return element.getChildText(name);
	}

	public static Integer getChildInteger(String name, Element element) {
		try {
			String text = element.getChildText(name);
			return new Integer(text);
		} catch (Throwable ex) {
		}
		return null;
	}

	public static Double getChildDouble(String name, Element element) throws NumberFormatException {
		try {
			String text = element.getChildText(name);
			return new Double(text);
		} catch (Throwable ex) {
		}
		return null;
	}

	public static void outputElement(Element element, Writer writer) throws Exception {
		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
		xmlOut.output(new Document(element), writer);
	}

	public static String outputElement(Element element) throws Exception {
		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
		String xmlString = xmlOut.outputString(new Document(element));
		return xmlString;
	}

	public static String outputElementMinimal(Element element) throws Exception {
		Format format = Format.getCompactFormat();
		format.setOmitDeclaration(true);
		XMLOutputter xmlOut = new XMLOutputter(format);
		String xmlString = xmlOut.outputString(new Document(element));
		return xmlString;
	}

	public static String outputElementOmitDeclaration(Element element) {
		Format format = Format.getPrettyFormat();
		format.setOmitDeclaration(true);
		XMLOutputter xmlOut = new XMLOutputter(format);
		String xmlString = xmlOut.outputString(new Document(element));
		return xmlString;
	}

	public static String outputDocument(Document document) throws Exception {
		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
		String xmlString = xmlOut.outputString(document);
		return xmlString;
	}

	public static String removeInvalidXMLChars(String value) {
		StringBuffer buffer = new StringBuffer();
		char[] array = value.toCharArray();
		for (int x = 0; x < array.length; x++) {
			if (Verifier.isXMLCharacter(array[x])) {
				buffer.append(array[x]);
			}
		}
		return buffer.toString();
	}

	public static Element createTextElement(String name, Object object, Element parentElement) {
		if (object == null) {
			return null;
		}

		String text = null;
		if (object instanceof Date) {
			Date date = (Date) object;

			text = formatDate(date);
		} else {
			text = object.toString();
		}
		Element element = new Element(name);
		// XMLOutputter outputter = new XMLOutputter();
		// text = outputter.escapeElementEntities(text);
		// text = removeInvalidXMLChars(text);
		element.setText(text);

		parentElement.addContent(element);

		return element;
	}

	public static Element createTextElement(String name, Object object, Namespace namespace, Element parentElement) {
		if (object == null) {
			return null;
		}

		String text = null;
		if (object instanceof File) {
			text = ((File) object).getAbsolutePath();
		} else {
			text = object.toString();
		}
		Element element = new Element(name, namespace);
		XMLOutputter outputter = new XMLOutputter();
		text = outputter.escapeElementEntities(text);
		text = removeInvalidXMLChars(text);
		element.setText(text);

		parentElement.addContent(element);

		return element;
	}

	public static void saveXML(Element element, File file) throws IOException {
		Document document = new Document();
		document.addContent(element);
		saveXML(document, file);
	}

	public static void saveXML(Document document, File file) throws IOException {
		File parentDir = file.getParentFile();
		if (!parentDir.exists()) {
			parentDir.mkdirs();
		}
		Format format = Format.getPrettyFormat();
		format.setLineSeparator("\n");
		XMLOutputter outputter = new XMLOutputter(format);
		String channelXMLStr = outputter.outputString(document);
		FileUtils.writeStringToFile(file, channelXMLStr, "UTF-8");
	}

	/**
	 * public static XmlPullParser parseDocumentSAX(InputStream input) throws
	 * Exception { XmlPullParserFactory factory =
	 * XmlPullParserFactory.newInstance("org.xmlpull.mxp1.MXParserFactory", null);
	 * factory.setNamespaceAware(false); factory.setValidating(false);
	 * XmlPullParser xpp = factory.newPullParser(); xpp.setInput(input, "UTF-8");
	 * return xpp; }
	 */
	public static File getChildFile(String name, Element element) throws Exception {
		String path = element.getChildTextTrim(name);
		if (StringUtils.isBlank(path)) {
			return null;
		}
		return new File(path);
	}

	public static Document parseDocument(File file) throws XMLException, IOException {
		String xml = FileUtils.readFileToString(file, "UTF-8");
		return parseDocument(xml);
	}

	public static Element parseElement(File file) throws XMLException, IOException {
		if (!file.exists()) {
			return null;
		}
		String xml = FileUtils.readFileToString(file, "UTF-8");
		return parseElement(xml);
	}

	public static Element parseElement(String xml) throws XMLException {
		Document document = parseDocument(xml);
		return document.getRootElement();
	}
  
	public static class XMLException extends Exception {
		public XMLException(String message, Throwable throwable) {
			super(message, throwable);
		}
	}
	
	public static Document parseDocument(String xml) throws XMLException {
		if (StringUtils.isBlank(xml))
			throw new IllegalArgumentException("xml blank"); 
		EntityResolver RESOLVER = new EmptyEntityResolver();
		SAXBuilder saxBuilder = new SAXBuilder(false);
		saxBuilder.setEntityResolver(RESOLVER);
		try {
			Document document = saxBuilder.build(new StringReader(xml));
			return document;
		} catch (Exception exception) {
			throw new XMLException(xml, exception);
		}
	}

	public static class EmptyEntityResolver implements EntityResolver {
		public InputSource resolveEntity(String publicId, String systemId) {
			InputSource EMPTY_INPUTSOURCE = new InputSource(new ByteArrayInputStream(new byte[0]));
			return EMPTY_INPUTSOURCE;
			// if (systemId != null && systemId.endsWith(".dtd")) return
			// EMPTY_INPUTSOURCE;
			// return null;
		}
	}
}
